#!/usr/bin/env python
# $Id: bgplg.py 6525fa8d3382 2012/09/10 23:32:19 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

import HTMLParser
import optparse
import re
import socket
import sys
import urllib2
import warnings

from xml.dom import minidom
import dns.resolver

from venya.lib.exception import GetOptError, IPError
from venya.lib import cmdparser, ip, log, stdlib

aslist = lambda value: [int(temp) for temp in value]

class ASNumberTree(object):
   def __init__(self, ASNumber):
      self._as = 0
      self._mainurl = "http://whois.arin.net/rest/asn"
      self._data = None
      self._handle = None
      self._name = None
      self._myname = None
      self._updated = None
      self._asname = None
      if ASNumber:
         try:
            self._as = int(ASNumber)
         except ValueError as err:
            raise ValueError(err.message)
         else:
            self._mainurl = "%s/%d" %(self._mainurl, self._as)
            self._myname = "%d" %self._as

      urldata = None
      try:
         urldata = urllib2.urlopen(self._mainurl)
      except urllib2.URLError as err:
         raise ValueError(err.message)
      except urllib2.HTTPError as err:
         raise ValueError(err.message)
      else:
         self._data = minidom.parseString(urldata.read())
         urldata.close()

   def _getASName(self):
      value = None
      mainquery = "AS%s.asn.cymru.com" %self._as

      if mainquery:
         data = dns.resolver.query(mainquery, "TXT")
         value = str(data.rrset).split("TXT")[1]
         value = value.replace("\"", "")
         value = value.split("| ")[-1]

      return value
         
   def parse(self):
      node = self._data.getElementsByTagName("asn")
      temp = node[0].getElementsByTagName("orgRef")[0]
      self._handle = temp.firstChild.nodeValue
      self._name = self._getASName()
      temp = node[0].getElementsByTagName("name")[0]
      self._asname = temp.firstChild.nodeValue

   def __getattr__(self, attr):
      retval = None
      if attr.lower() == "handle":
         retval = self._handle
      elif attr.lower() == "org":
         retval = self._name
      elif attr.lower() == "name":
         retval = self._asname

      return retval.encode('utf-8')

   def __str__(self):
      return self._myname.encode('utf-8')

   def __int__(self):
      return int(self._as)

class LGParser(HTMLParser.HTMLParser):
   def __init__(self, aspath = False, *args):
      HTMLParser.HTMLParser.__init__(self)
      self._data = {}
      self._tags = []
      self._htmltag = []
      self._aspath = False
      self._asinfo = []

      if aspath:
         self._aspath = True

      for ix in args:
         self._htmltag.append(ix)

   def handle_starttag(self, tag, attrs):
      if tag in self._htmltag:
         self._tags.append(tag)

   def handle_endtag(self, tag):
      if tag in self._htmltag:
         self._tags.pop()

   def handle_data(self, data):
      if len(self._tags) > 0:
         self._data[self._tags[-1]] = data

   def GetASPath(self, data):
      raspath = re.compile("^((\d{1,5}[\s]*)+)$")
      aspath = []
      match = None
      for line in data.split("\n"):
         match = raspath.search(line.strip())
         if match:
            aspath = match.groups()[0].replace(",", "").split(" ")

      if len(aspath) == 0 and data.find("aggregated by") >= 0:
         raspath = re.compile("^((\d{1,5}[\s]*)+)\(.*\)$")
         for line in data.split("\n"):
            match = raspath.search(line.strip().replace(",", ""))
            if match:
               aspath = match.groups()[0].strip().split(" ")

      for ix in aspath:
         ashandle = ASNumberTree(ix)
         if int(ashandle) not in aslist(self._asinfo):
            ashandle.parse()
            self._asinfo.append(ashandle)

   def __getattr__(self, attr):
      retval = None
      if attr.lower() in self._data.keys():
         retval = self._data[attr].strip()
         if self._aspath:
            self.GetASPath(retval)
      else:
         if attr.lower() == "asinfo":
            retval = self._asinfo

      return retval

class bgplg(cmdparser.GetOpt):
   option_list = (
         optparse.make_option("-a", "--asn", action = "store_true",
            help = "Show BGP AS number details for a route", default = False,
            dest = "asn"),
         optparse.make_option("-s", "--summary", action = "store",
            type = "choice", choices = ["4", "6"], default = 0,
            help = "Show BGP summary for address family", dest = "summary"),
   )

   def __init__(self, **Keywords):
      super(stdlib.my_class(self), self).__init__(**Keywords)
      self._urlhandle = None
      self._log = log.StreamLog(stdlib.my_name(self))
      self._asn = False
      self._bogon = []

   def urlget(self, url, value):
      retval = None
      if url:
         parser = LGParser(self._asn, "pre")
         data = None
         try:
            if value:
               url = "%s=%s" %(url, value)

            data = urllib2.urlopen(url)
         except urllib2.URLError as err:
            self.warn(err.message)
         except urllib2.HTTPError as err:
            self.warn(err.message)
         else:
            parser.feed(data.read())
            data.close()
            retval = parser

      return retval

   def isbogon(self, ipaddr):
      retval = False
      query = None
      if ipaddr.version == 4:
         domain = "v4.fullbogons.cymru.com."
      elif ipaddr.version == 6:
         domain = "v6.fullbogons.cymru.com."

      query = "%s.%s" %(ipaddr.reverse, domain)
      test = None
      try:
         test = dns.resolver.query(query)
      except dns.resolver.NXDOMAIN:
         pass
      else:
         self._bogon.append(ipaddr)
         retval = True

      return retval

   def GetIPv4(self, address):
      retval = None
      mainurl = "http://as9370.bgp4.jp/lg.cgi?query=1&arg"
      if not self.isbogon(address):
         temp = self.urlget(mainurl, str(address))
         if temp is not None:
            retval = temp

      return retval

   def GetIPv6(self, address):
      retval = None
      mainurl = "http://as9370v6.bgp4.jp/lg.cgi?query=1&arg"
      value = str(address).replace(":", "%3A").replace("/", "%2F")
      if not self.isbogon(address):
         temp = self.urlget(mainurl, value)
         if temp is not None:
            retval = temp

      return retval

   def GetIP(self, Args = []):
      retval = []
      for item in Args:
         temp = None
         try:
            temp = socket.getaddrinfo(item, 0)
            if temp:
               addr = ip.ip(str(temp[0][-1][0]))
               ipinfo = None
               if addr.version == 6:
                  ipinfo = self.GetIPv6(addr)
               elif addr.version == 4:
                  ipinfo = self.GetIPv4(addr)

               if ipinfo is not None:
                  retval.append(ipinfo)
         except socket.gaierror as (errno, err):
            self.warn("%s %s", err, item)
            continue
         except IPError as err:
            self.warn(err)
            continue

      return retval

   def GetSummary(self, Af):
      retval = None
      if Af == 6:
         mainurl = "http://as9370v6.bgp4.jp/lg.cgi?query=2&arg="
         retval = self.urlget(mainurl, None).pre
      elif Af == 4:
         mainurl = "http://as9370.bgp4.jp/lg.cgi?query=2&arg="
         retval = self.urlget(mainurl, None).pre

      return retval

   @cmdparser.getopts
   def handle(self):
      startc = '\033[96m'
      endc = '\033[0m'

      item = []
      try:
         item = self.arguments[1:]
      except IndexError as err:
         self.error("Need v4/v6 address/asnumber for look up!")

      self._asn = self.options.asn
      for value in self.GetIP(item):
         startc = '\033[96m'
         print(startc + "\n" + str(value.pre) + endc)
         if self.options.asn and len(value.asinfo):
            startc = '\033[90m'
            print(startc + "\nASN information: ")
            startc = '\033[93m'
            for ix in value.asinfo:
               print(startc + "%s: %s, %s" %(ix, ix.name, ix.org) + endc)

      if self._bogon:
         print(endc)
         for ix in self._bogon:
            startc = '\033[91m'
            print(startc + "%s is a BOGON address" %(ix))

         print(endc)

      if self.options.summary:
         retval = self.GetSummary(int(self.options.summary))
         if retval:
            startc = '\033[92m'
            print(startc + "\n" + str(retval) + "\n" + endc)

if __name__ == "__main__":
   lghandle = None
   try:
      lghandle = bgplg(version = 1.0, args = sys.argv, usage = "ip/ipv6")
      if len(lghandle) <= 1:
         lghandle.printhelp()
      else:
         lghandle.handle()
   except GetOptError, message:
      print(message)
      sys.exit(1)
